/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.wdb.servlet;

import com.fasterxml.jackson.databind.node.ObjectNode;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.lang.MissingMethodException;
import groovy.lang.MissingPropertyException;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.vacoor.nothing.common.json.Jacksons;
import org.vacoor.nothing.common.util.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;

//import org.codehaus.groovy.tools.shell.util.NoExitSecurityManager;

/**
 * @author vacoor
 */
public class GroovyShellServlet extends HttpServlet {
    private static final String SCRIPT_FILENAME = "groovy_evaluate_context";
    private static final String GROOVY_OUT_KEY = "out";
    private static final String GROOVY_SHELL_KEY = "groovy_shell";
    private static final String SCRIPT_PARAM_NAME = "script";


    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        //System.setSecurityManager(new NoExitSecurityManager());
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String script = request.getParameter(SCRIPT_PARAM_NAME);
        if (StringUtils.hasText(script)) {
            StringWriter writer = new StringWriter();
            GroovyShell shell = getGroovyShell(request, response);

            shell.setVariable("request", request);
            shell.setVariable("context", getServletContext());
            shell.setVariable(GROOVY_OUT_KEY, writer);  // binding
            try {
                Object result = shell.evaluate(script, SCRIPT_FILENAME);

                writer.write("==> " + String.valueOf(result));
            } catch (Throwable e) {
                handleException(writer, e);
            } finally {
            }

            writer.write("\r\n");
            ObjectNode objectNode = Jacksons.createObjectNode()
                    .put(SCRIPT_PARAM_NAME, script)
                    .put("result", writer.toString());
            response.getWriter().write(objectNode.toString());
        }
    }

    protected GroovyShell getGroovyShell(HttpServletRequest request, HttpServletResponse response) {
        HttpSession session = request.getSession();
        GroovyShell shell = (GroovyShell) session.getAttribute(GROOVY_SHELL_KEY);

        if (shell == null) {
            CompilerConfiguration conf = new CompilerConfiguration();
            // conf.setScriptBaseClass(ExtScript.class.getName());
            Map<String, Object> binding = new HashMap<String, Object>();
            shell = new GroovyShell(Thread.currentThread().getContextClassLoader(), new Binding(binding), conf);

            tryLoadSpringBeans(binding);

            session.setAttribute(GROOVY_SHELL_KEY, shell);
        }

        return shell;
    }

    /**
     * 尝试从 Spring WebApplicationContext 中加载 Bean
     */
    protected void tryLoadSpringBeans(Map<String, Object> binding) {
        WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        if (ctx != null) {
            String[] names = ctx.getBeanDefinitionNames();
            for (String name : names) {
                Object bean = ctx.getBean(name);
                binding.put(name, bean);
            }
        }
    }

    /**
     * 异常处理
     */
    protected void handleException(Writer writer, Throwable e) throws IOException {
        final Formatter formatter = new Formatter(writer);

        if (e instanceof MissingPropertyException) {
            MissingPropertyException missPropEx = (MissingPropertyException) e;
            String name = missPropEx.getType().getName();
            String prop = missPropEx.getProperty();

            if (SCRIPT_FILENAME.equalsIgnoreCase(name)) {
                formatter.format("\u001b[31;1mUnknown property\u001b[0m: %s", prop);
                return;
            }
        }

        if (e instanceof MissingMethodException) {
            MissingMethodException missMethodEx = (MissingMethodException) e;
            String name = missMethodEx.getType().getName();
            String method = missMethodEx.getMessage();

            if (SCRIPT_FILENAME.equals(name)) {
                formatter.format("\u001b[31;1mUnknown method\u001b[0m: %s", method);
                return;
            }
        }

        formatter.format("\u001b[31;1mERROR \u001b[0m%s\r\n", e.getClass().getName());
        writer.write("\u001b[31;1m");
        e.printStackTrace(new PrintWriter(writer));
        writer.write("\u001b[0m\r\n");
    }
}
